#include "precomp.h"
#ifdef _WIN32
#include <locale.h>
#endif

static_assert(sizeof(LUA_INTEGER) >= 8, "LUA_INTEGER size must be at least 8 bytes");

int _returnPointerList(lua_State* L, const CPointerList& plist)
{
	lua_createtable(L, plist.size(), 0);
	size_t len = 1;
	for (auto p : plist) {
#if 1
		lua_pushinteger(L, (uint64_t)p);
#else
		char buf[32];
		sprintf(buf, "%p", p);
		lua_pushstring(L, buf);
#endif
		lua_rawseti(L, -2, len);
		len++;
	}
	return 1;
}

static char toHexChar(int value)
{
	return (value < 10) ? ('0' + value) : ('a' + value - 10);
}

static uint8_t fromHex(uint8_t val)
{
	if (val >= '0' && val <= '9')
		return val - '0';
	if (val >= 'a' && val <= 'f')
		return val - 'a' + 10;
	if (val >= 'F' && val <= 'F')
		return val - 'A' + 10;
	assert(0);
	return 0;
}

static int lua_string_to_hex(lua_State* L)
{
	size_t len, i;
	std::string tgt;
	const char* p = luaL_checklstring(L, 1, &len);
	for (i = 0; i < len; i++, p++) {
		uint8_t uc = *p;
		tgt += toHexChar(uc >> 4);
		tgt += toHexChar(uc & 0xf);
	}
	lua_pushlstring(L, tgt.data(), tgt.size());
	return 1;
}

static int lua_string_from_hex(lua_State* L)
{
	size_t len, i;
	const char* p = luaL_checklstring(L, 1, &len);
	if (len & 1)
		luaL_error(L, "Invalid hex string");
	std::string tgt;
	for (i = 0; i < len; i += 2, p += 2) {
		uint8_t a = *p;
		uint8_t b = p[1];
		if (!isxdigit(a) || !isxdigit(b))
			luaL_error(L, "Invalid hex string");
		tgt += char((fromHex(a) << 4) | fromHex(b));
	}
	lua_pushlstring(L, tgt.data(), tgt.size());
	return 1;
}

static int lua_getenv(lua_State* L)
{
	const char* name = luaL_checkstring(L, 1);
	std::string s = rtk_get_env(name);
	lua_pushlstring(L, s.c_str(), s.size());
	return 1;
}

static int lua_setenv(lua_State* L)
{
	const char* name = luaL_checkstring(L, 1);
	const char* value = lua_tostring(L, 2);
	if (!value)
		value = "";
	rtk_set_env(name, value);
	return 0;
}

static int lua_setlocale(lua_State* L)
{
	int cat = lua_tointeger(L, 1);
	const char* p = lua_tostring(L, 2);
	p = setlocale(cat, p);
	if (p) {
		lua_pushstring(L, p);
		return 1;
	}
	return 0;
}

extern std::vector<lua_CFunction>* get_init_funcs()
{
	static std::vector<lua_CFunction> gamex_init_funcs;
	return &gamex_init_funcs;
}

std::vector<luaL_Reg>& get_gamex_lib_funcs()
{
	static std::vector<luaL_Reg> funcs;
	return funcs;
}

static int lua_searchProcessString(lua_State* L)
{
	size_t len;
	int pid = lua_tointeger(L, 1);
	const char* str = luaL_checklstring(L, 2, &len);
	CPointerList plist = searchProcessString(pid, str, len);
	return _returnPointerList(L, plist);
}

static int lua_searchProcessInt32(lua_State* L)
{
	int pid = lua_tointeger(L, 1);
	int val = lua_tointeger(L, 2);
	CPointerList plist = searchProcessString(pid, (const char*)&val, 4);
	return _returnPointerList(L, plist);
}

static int lua_searchProcessInt64(lua_State* L)
{
	int pid = lua_tointeger(L, 1);
	int val = lua_tointeger(L, 2);
	CPointerList plist = searchProcessString(pid, (const char*)&val, 8);
	return _returnPointerList(L, plist);
}

#define DEFINE_INT_FUNC(Type, LuaType)                                                  \
	static int lua_read_##Type(lua_State* L)                                            \
	{                                                                                   \
		size_t buf_size;                                                                \
		const char* buf = luaL_checklstring(L, 1, &buf_size);                           \
		size_t offset = luaL_checkinteger(L, 2);                                        \
		size_t type_size = sizeof(Type##_t);                                            \
                                                                                        \
		if (offset + type_size > buf_size) {                                            \
			luaL_error(L, "Offset %zu out of range (buffer size: %zu, type size: %zu)", \
				offset, buf_size, type_size);                                           \
		}                                                                               \
                                                                                        \
		Type##_t val = 0;                                                               \
		memcpy(&val, buf + offset, type_size);                                          \
		lua_push##LuaType(L, val);                                                      \
		return 1;                                                                       \
	}                                                                                   \
	static int lua_write_##Type(lua_State* L)                                           \
	{                                                                                   \
		std::string ret;                                                                \
		int i;                                                                          \
		for (i = 1; i <= lua_gettop(L); i++) {                                          \
			Type##_t val = lua_tointeger(L, i);                                         \
			ret.append((char*)&val, sizeof(val));                                       \
		}                                                                               \
		lua_pushlstring(L, ret.c_str(), ret.size());                                    \
		return 1;                                                                       \
	}

// 为每种类型生成函数
DEFINE_INT_FUNC(int8, integer)
DEFINE_INT_FUNC(int16, integer)
DEFINE_INT_FUNC(int32, integer)
DEFINE_INT_FUNC(int64, integer)
DEFINE_INT_FUNC(uint8, integer)
DEFINE_INT_FUNC(uint16, integer)
DEFINE_INT_FUNC(uint32, integer)
DEFINE_INT_FUNC(uint64, integer)

static int lua_read_char(lua_State* L)
{
	size_t buf_size;
	const char* buf = luaL_checklstring(L, 1, &buf_size);
	size_t offset = lua_tointeger(L, 2);
	if (offset >= buf_size) {
		luaL_error(L, "Script out of range.");
	}
	char str[2];
	str[0] = buf[offset];
	str[1] = 0;
	lua_pushstring(L, str);
	return 1;
}

extern "C" DLL_EXPORT int luaopen_libgamex(lua_State* L)
{
	lua_newtable(L);

	regfunc(string_to_hex);
	regfunc(string_from_hex);
	regfunc(getenv);
	regfunc(setenv);
	regfunc(setlocale);

	regconst(LC_ALL);
	regconst(LC_CTYPE);
	regconst(LC_NUMERIC);
	regconst(LC_TIME);
	regconst(LC_COLLATE);
	regconst(LC_MONETARY);
#ifdef __linux__
	regconst(LC_PAPER);
	regconst(LC_NAME);
	regconst(LC_ADDRESS);
	regconst(LC_TELEPHONE);
	regconst(LC_MEASUREMENT);
	regconst(LC_MESSAGES);
#endif
	regfunc(read_char);
#define reg_intfunc(n) \
	regfunc(read_##n); \
	regfunc(write_##n)

	reg_intfunc(int8);
	reg_intfunc(int16);
	reg_intfunc(int32);
	reg_intfunc(int64);
	reg_intfunc(uint8);
	reg_intfunc(uint16);
	reg_intfunc(uint32);
	reg_intfunc(uint64);
	regfunc(searchProcessString);
	regfunc(searchProcessInt32);
	regfunc(searchProcessInt64);
	for (auto f : get_gamex_lib_funcs()) {
		lua_pushstring(L, f.name);
		lua_pushcfunction(L, f.func);
		lua_rawset(L, -3);
	}

	int target = lua_gettop(L);
	luartk_embed_script_with_ret(L, "utils");
	luartk_merge_table(L, target);

	for (auto f : *get_init_funcs()) {
		f(L);
	}
	return 1;
}
